﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.CompilerServices;

namespace 图片裁剪压缩工具
{
    public class ImageEncoder
    {
        public struct Size
        {
            public int SrcWidth { get; private set; }
            public int SrcHeight { get; private set; }
            public int Index { get; private set; }

            public Size(int width, int height)
            {
                this = new Size(width, height, 0);
            }

            private Size(int width = 0, int height = 0, int index = 0)
            {
                SrcWidth = width;
                SrcHeight = height;
                Index = index;
            }

            private bool Get(int src, out int v)
            {
                if (Index < 10)
                {
                    v = (10 - Index) * 10 * src / 100;
                }
                else if (Index < 20)
                {
                    v = (20 - Index) * src / 100;
                }
                else if (Index < 30)
                {
                    v = (int)((30 - Index) * src / 1000.0);
                }
                else if (Index < 40)
                {
                    v = (int)((40 - Index) * src / 10000.0);
                }
                else
                {
                    v = 0;
                }
                return v > 0;
            }

            public int Width => Get(SrcWidth, out var v) ? v : 0;

            public int Height => Get(SrcHeight, out var v) ? v : 0;

            public bool Empty()
            {
                return Width == 0 || Height == 0;
            }
            public Size Take(int step = 0)
            {
                return new Size(SrcWidth, SrcHeight, step);
            }
            public static Size operator --(Size size)
            {
                size.Index++;
                return size;
            }

            public static bool operator >(Size size1, Size size2)
            {
                return size1.SrcWidth == size2.SrcWidth && size1.SrcHeight == size2.SrcHeight && size1.Index < size2.Index;
            }

            public static bool operator <(Size size1, Size size2)
            {
                return size2 > size1;
            }

            public static bool operator >=(Size size1, Size size2)
            {
                return size1.SrcWidth == size2.SrcWidth && size1.SrcHeight == size2.SrcHeight && size1.Index <= size2.Index;
            }

            public static bool operator <=(Size size1, Size size2)
            {
                return size2 >= size1;
            }

            public static bool operator ==(Size size1, Size size2)
            {
                return size1.SrcWidth == size2.SrcWidth && size1.SrcHeight == size2.SrcHeight && size1.Index == size2.Index;
            }

            public static bool operator !=(Size size1, Size size2)
            {
                return size1.SrcWidth != size2.SrcWidth || size1.SrcHeight != size2.SrcHeight || size1.Index != size2.Index;
            }

            public override int GetHashCode()
            {
                return SrcWidth ^ SrcHeight ^ Index;
            }

            public override bool Equals(object? obj)
            {
                return obj is Size size && this == size;
            }

            public override string ToString()
            {
                return $"{{{Width},{Height},{Index}}}";
            }
        }

        public struct Quality
        {
            public int SrcQ { get; private set; }

            public int Index { get; private set; }

            public int Value
            {
                get
                {
                    int result;
                    if (SrcQ > 95)
                    {
                        result = SrcQ - 4 * Index;
                    }
                    else if (SrcQ > 90)
                    {
                        result = SrcQ - 3 * Index;
                    }
                    else if (SrcQ > 85)
                    {
                        result = SrcQ - 2 * Index;
                    }
                    else
                    {
                        result = SrcQ - Index;
                    }
                    return result;
                }
            }
            public Quality(int q, int index = 0)
            {
                SrcQ = q;
                Index = index;
            }

            public void NextIndex()
            {
                ++Index;
            }
            public static Quality operator --(Quality q)
            {
                q.SrcQ--;
                return q;
            }

            public static bool operator >(Quality q1, Quality q2)
            {
                return q1.Value > q2.Value;
            }

            public static bool operator <(Quality q1, Quality q2)
            {
                return q1.Value < q2.Value;
            }

            public static bool operator >=(Quality q1, Quality q2)
            {
                return q1.Value >= q2.Value;
            }

            public static bool operator <=(Quality q1, Quality q2)
            {
                return q1.Value <= q2.Value;
            }
        }

        public class Encoder
        {
            public Image SrcImage { get; private set; }
            public Size BaseSize { get; private set; }
            public Quality BaseQ { get; private set; }
            public Size LimitSize { get; private set; }
            public Quality LimitQ { get; private set; }
            public int LimitFileSize { get; private set; }

            public Encoder(Image src, int q, bool sizeLock = true, bool qLock = true, int limitFileSize = 1024 * 1024 * 10)
            {
                SrcImage = src;
                BaseSize = new Size(src.Width, src.Height);
                BaseQ = new Quality(q);
                LimitSize = sizeLock ? BaseSize : BaseSize.Take(32);
                LimitQ = qLock ? BaseQ : new Quality(10);
                LimitFileSize = limitFileSize;
            }

            public bool Do(string fileName, out Size endSize, out int endQ, out int outFileSize)
            {
                endSize = default;
                endQ = 0;
                outFileSize = 0;
                RecyclableMemoryStream ms = new RecyclableMemoryStream(10485760);
                EncoderParameters encoderParams = new EncoderParameters(1);
                ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders()[1];

                for (Size size = BaseSize; size >= LimitSize && !size.Empty(); size--)
                {
                    int stepLen = (BaseSize != LimitSize) ? 4 : 8;
                    for (Quality q = BaseQ; q.Index < stepLen && q >= LimitQ; q.NextIndex())
                    {
                        using (var bitmap = new Bitmap(SrcImage, size.Width, size.Height))
                        {
                            encoderParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, q.Value);
                            ms.Reset();
                            bitmap.Save(ms, codec, encoderParams);
                            if (ms.Length <= LimitFileSize)
                            {
                                encoderParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, q.Value);
                                bitmap.Save(fileName, codec, encoderParams);
                                endSize = size;
                                endQ = q.Value;
                                outFileSize = (int)ms.Length;
                                string path = fileName + ".info.txt";
                                var info = $"宽度:{endSize.Width}\n" +
                                    $"高度:{endSize.Height}\n" +
                                    $"原宽:{endSize.Width}\n" +
                                    $"原高:{endSize.Height}\n" +
                                    $"质量:{endQ}\n" +
                                    $"文件大小:{outFileSize / 1024.0:f2}K\n" +
                                    $"文件Sha1:{Utils.FileContentSha1(fileName)}";
                                File.WriteAllText(path, info);
                                return true;
                            }
                        }
                        endSize = size;
                        endQ = q.Value;
                        outFileSize = (int)ms.Length;
                    }
                }
                return false;
            }
        }
    }
}